import SSZipArchive
import SQLite

let gosnMessage = OsnMessage()

class OsnMessage {
    
    func fromJson(_ json:[String:Any], _ data:[String:Any]) -> DMCCMessage {
        let m = DMCCMessage()
        m.target = json["from"] as? String ?? ""
        m.direction = OsnMessageDirection.Receive
        m.status = OsnMessageStatus.Unread
        m.msgHasho = json["hash0"] as? String ?? ""
        m.msgHash = json["hash"] as? String ?? ""
//        m.timestamp = json["timestamp"] as? Int64 ?? 0
        if ((json["timestamp"] as? Int64) != nil) {
            m.timestamp = json["timestamp"] as? Int64 ?? 0
        }else{
            let stringNumber = json["timestamp"] as? String
            m.timestamp = Int64(stringNumber!) ?? -1
        }
        var content = data
        if(content.contains(where: { (key: String, value: Any) in
            key == "content"
        })){
            if content["content"] is String {
                content = OsnUtils.toJson(content["content"] as? String) ?? [:]
            }else if content["content"] is [String:Any] {
                content = content["content"] as! [String:Any]
            }else{
                print("invalid content \(content)")
                content = [:]
            }
        }
        m.content = gosnContent.fromJson(content)
        m.msgType = if let content = m.content as? OsnMessageContent { content.contentType } else { OsnMessageContentType.unknown }
        if let t = data["originalUser"] as? String {
            m.originalUser = t
        }else if let t = content["originalUser"] as? String {
            m.originalUser = t
        }else{
            m.originalUser = json["from"] as? String ?? ""
        }
        return m
    }
    func getMessageList(_ target: String, _ timestamp:Int64, _ size:Int) ->[DMCCMessage]{
        return dbMessage.listMessageInfo(target, timestamp, size)
    }
    func getMessageFromHash(_ hash: String) -> DMCCMessage? {
        return dbMessage.getMessageFromHash(hash)
    }
    func getMessageFromHasho(_ hash: String) -> DMCCMessage? {
        return dbMessage.getMessageFromHasho(hash)
    }
    //    func getMessageWithID(_ hash: String) -> DMCCMessage? {
    //        return dbMessage.get
    //    }
    func setMessage(_ message:DMCCMessage) -> Bool {
        return dbMessage.addMessageInfo(message)
    }
    
    func clearMessage(_ osnId: String) ->Bool{
        return dbMessage.delMessageInfo(osnId)
    }
    
    func delOneMessage(_ hash: String) ->Bool{
        return dbMessage.delOneMessage(hash)
    }
    
    func makeTextMessage(_ text:String)->OsnTextMessageContent{
        let message = OsnTextMessageContent()
        message.data = text
        return message
    }
    func makeCardMessage(_ cardId:String)->OsnCardMessageContent{
        let card = OsnCardMessageContent()
        card.target = cardId
        card.fromUser = gimService.mOsnId
        let type = gosnQuery.getOsnIdType(cardId)
        if(type == OsnIdType.user){
            let user = gosnUser.getUserInfoDefault(cardId)
            card.cardType = "user"//OsnCardType.User
            card.name = user.name
            card.portrait = user.portrait
            card.displayName = user.alias
        }else if(type == OsnIdType.group){
            let group = gosnGroup.getGroupInfoDefault(cardId)
            card.cardType = "group"//OsnCardType.Group
            card.name = group.name
            card.portrait = group.portrait
            card.displayName = group.alias
        }else if(type == OsnIdType.dapp){
            card.cardType = "litapp"//OsnCardType.Litapp
            if let dapp = gosnDapp.getDappInfo(cardId){
                card.name = dapp.name
                card.param = dapp.param
                card.displayName = dapp.displayName
                card.portrait = dapp.portrait
                card.theme = dapp.theme
                card.url = dapp.url
                card.info = dapp.info
            }
        }
        return card
    }
    func makeMediaContent(_ content:OsnMediaMessageContext, _ path:String, _ url:String, _ password:String){
        guard let attribs = try? FileManager.default.attributesOfItem(atPath: path) else {return}
        if path.count > 0 {
            content.name = URL(string: path)?.lastPathComponent ?? ""
            content.fileSize = Int(attribs[.size] as? Int64 ?? 0)
            content.localPath = path
        }
        content.remoteUrl = url
        content.decKey = password
    }
    
    func makeFileContent(_ content:OsnMediaMessageContext, _ path:String,_ name:String, _ url:String, _ password:String){
        guard let attribs = try? FileManager.default.attributesOfItem(atPath: path) else {return}
        if path.count > 0 {

            let newURL = URL(fileURLWithPath: path).deletingLastPathComponent().appendingPathComponent(name)

            do {
                try FileManager.default.moveItem(at: URL(fileURLWithPath: path), to: newURL)
                print("文件名更改成功！")
            } catch {
                print("文件名更改失败：\(error)")
            }
            let newPath = newURL.path
//            let p = newURL.path
//            let res0 = FileManager.default.fileExists(atPath: p)
//            let res2 = FileManager.default.fileExists(atPath: newPath)
//            if newPath.hasPrefix("file://") {
//                newPath = newPath.replacingOccurrences(of: "file://", with: "")
//            }
//            let res3 = FileManager.default.fileExists(atPath: newPath)
            
            content.name = name
            content.fileSize = Int(attribs[.size] as? Int64 ?? 0)
            content.localPath = newPath
//            content.name = URL(string: path)?.lastPathComponent ?? ""
//            content.fileSize = Int(attribs[.size] as? Int64 ?? 0)
//            content.localPath = path
        }
        content.remoteUrl = url
        content.decKey = password
    }
    
    func makeImageMessage(_ path:String, _ url:String, _ password:String)->OsnImageMessageContent{
        let media = OsnImageMessageContent()
        makeMediaContent(media, path, url, password)
        media.width = 0
        media.height = 0
        return media
    }
    func makeVideoMessage(_ path:String, _ url:String, _ password:String)->OsnVideoMessageContent{
        let media = OsnVideoMessageContent()
        makeMediaContent(media, path, url, password)
        media.duration = 0
        return media
    }
    func makeFileMessage(_ path:String, _ url:String,_ name:String, _ password:String)->OsnFileMessageContent{
        let media = OsnFileMessageContent()
//        makeFileContent(media, path, name, password)
        makeFileContent(media, path, name, url, password)
        return media
    }
    func makeVoiceMessage(_ path:String, _ url:String, _ password:String)->OsnVoiceMessageContent{
        let media = OsnVoiceMessageContent()
        makeMediaContent(media, path, url, password)
        let pathStr = URL(string: path)
        let avAudioPlayer = try? AVAudioPlayer(contentsOf: pathStr!)
        let duration = avAudioPlayer?.duration ?? 0
        media.duration = Int(duration)
        return media
    }
    func makeStickerMessage(_ path:String, _ url:String, _ password:String)->OsnStickerMessageContent{
        let media = OsnStickerMessageContent()
        makeMediaContent(media, path, url, password)
        media.width = 0
        media.height = 0
        return media
    }
    func withMentionInfo(_ content:OsnMessageContent, _ type:OsnMentionedType, _ users:[String]){
        content.mentionedType = type
        content.mentionedTargets = users
    }
    func withQuoteInfo(_ content:OsnMessageContent, _ message:DMCCMessage){
        if(content.quoteInfo == nil){
            content.quoteInfo = OsnQuoteInfo()
        }
        content.quoteInfo!.messageHash = message.msgHash
        content.quoteInfo!.userId = message.originalUser
    }
    func sendMessage(_ osnId:String, _ content:OsnMessageContent) async->(DMCCMessage, OspnError?){
        return await gimService.sendMessage(osnId, content)
    }
    func sendTextMessage(_ osnId:String, _ text:String)async->(DMCCMessage, OspnError?){
        let message = makeTextMessage(text)
        return await sendMessage(osnId, message)
    }
    func sendCardMessage(_ osnId:String, _ cardId:String)async->(DMCCMessage, OspnError?){
        let message = makeCardMessage(cardId)
        return await sendMessage(osnId, message)
    }
    func sendDappMessage(_ osnId:String, _ dapp:[String:Any])async->(DMCCMessage, OspnError?){
        //let message = gosnContent.cardContent(dapp)
        
        let message = OsnCardMessageContent.fromJson(dapp)
        return await sendMessage(osnId, message)
    }
    func sendMediaMessage(_ type:OsnMessageContentType, _ osnId:String, _ path:String)async->(DMCCMessage?, OspnError?){
        let password = "\(OsnUtils.getTimeStamp())"
        let (url, err) = await uploadMedia(path, password, OsnUploadType.temp)
        
        //        let url = FileTool.shared.uploadFile(filePath: path, password: password) { url in
        //
        //        } progress: { uploaded, total in
        //
        //        } error: { err in
        //
        //        }
        
        if err != nil{
            return (nil, err)
        }
        
        var message:OsnMessageContent
        if(type == OsnMessageContentType.image){
            message = makeImageMessage(path, url!, password)
        }else if(type == OsnMessageContentType.video){
            message = makeVideoMessage(path, url!, password)
        }else if(type == OsnMessageContentType.file){
//            message = makeFileMessage(path, url!, password)
            message = makeFileMessage(path, url!, "", password)
        }else if(type == OsnMessageContentType.voice){
            message = makeVoiceMessage(path, url!, password)
        }else if(type == OsnMessageContentType.sticker){
            message = makeStickerMessage(path, url!, password)
        }else{
            return (nil, OspnError.UnknowType)
        }
        return await sendMessage(osnId, message)
    }
    func sendImageMessage(_ osnId:String, _ path:String) async->(DMCCMessage?, OspnError?){
        return await sendMediaMessage(OsnMessageContentType.image, osnId, path)
    }
    func sendVideoMessage(_ osnId:String, _ path:String) async->(DMCCMessage?, OspnError?){
        return await sendMediaMessage(OsnMessageContentType.video, osnId, path)
    }
    func sendFileMessage(_ osnId:String, _ path:String,_ name:String) async->(DMCCMessage?, OspnError?){
        let password = "\(OsnUtils.getTimeStamp())"
        let (url, err) = await uploadMedia(path, password, OsnUploadType.temp)
        if err != nil{
            return (nil, err)
        }
        
        var message:OsnMessageContent
        message = makeFileMessage(path, url!, name, password)
        return await sendMessage(osnId, message)
    }
    func sendVoiceMessage(_ osnId:String, _ path:String) async->(DMCCMessage?, OspnError?){
        return await sendMediaMessage(OsnMessageContentType.voice, osnId, path)
    }
    func sendStickerMessage(_ osnId:String, _ path:String) async->(DMCCMessage?, OspnError?){
        return await sendMediaMessage(OsnMessageContentType.sticker, osnId, path)
    }
    func tempFile() -> String {
        let documentsPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!
        let uuid = UUID().uuidString
        return "\(documentsPath)/\(uuid)"
    }
    func tempPath() -> String {
        let documentsPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!
        return "\(documentsPath)/"
    }
    func uploadFile(_ path:String, onProgress:((Int, Int) -> Void)? = nil) async ->String?{
        let name = URL(string: path)?.lastPathComponent ?? ""
        let (url, _) = await ghttpService.uploadFile(name, path, .temp, onProgress)
        return url
    }
    func uploadMedia(_ path:String, _ password:String, _ type:OsnUploadType, onProgress:((Int, Int) -> Void)? = nil) async ->(String?, OspnError?){
        let zipPath = tempFile()+".zip"
        
        guard SSZipArchive.createZipFile(atPath: zipPath, withFilesAtPaths: [path], withPassword: password) else {
            return (nil, OspnError.Encode)
        }
        let name = URL(fileURLWithPath: zipPath).lastPathComponent
        return await ghttpService.uploadFile(name, zipPath, type, onProgress)
    }
    func downloadFile(_ url:String) async -> (String?, OspnError?) {
        let filePath = tempPath() + URL(fileURLWithPath: url).lastPathComponent
        if let err = await ghttpService.downloadFile(url, filePath) {
            return (nil, err)
        }
        return (filePath, nil)
    }
    func getMediaFile(_ path:String) ->String? {
        do{
            let fileManager = FileManager.default
            let contents = try fileManager.contentsOfDirectory(atPath: path)
            return path + "/" + contents.first!
            //            for url in contents {
            //                return url.path
            //            }
        }catch{
            print("\(error)")
        }
        return nil
    }
    func decodeMedia(_ message:DMCCMessage) async -> Bool {
        if !isMedia(message){
            return false
        }
        if !isEncoded(message){
            return true
        }
        guard let media = message.content as? OsnMediaMessageContext else {return false}
        let filePath = tempPath() + URL(fileURLWithPath: media.remoteUrl).lastPathComponent
        if (await ghttpService.downloadFile(media.remoteUrl, filePath)) != nil {
            return false
        }
        var rawFile:String?
        let rawPath = tempPath()+UUID().uuidString
        do{
            try SSZipArchive.unzipFile(atPath: filePath, toDestination: rawPath, overwrite: true, password: media.decKey)
            rawFile = getMediaFile(rawPath)
        }catch{
            print("\(error)")
        }
        if let rawFile = rawFile {
            
            if message.content is OsnFileMessageContent {
                let content = message.content as? OsnFileMessageContent
                let newURL = URL(fileURLWithPath: rawFile).deletingLastPathComponent().appendingPathComponent(content!.name)

                do {
                    try FileManager.default.moveItem(at: URL(fileURLWithPath: rawFile), to: newURL)
                    print("文件名更改成功！")
                } catch {
                    print("文件名更改失败：\(error)")
                }
                let newPath = newURL.path
                media.localPath = newPath
            }else{
                media.localPath = rawFile
            }
            
            
            _ = gosnMessage.setMessage(message)
        }
        do{
            try FileManager.default.removeItem(atPath: filePath)
        }catch{
            print("\(error)")
        }
        return true
    }
    func isEncoded(_ message:DMCCMessage) -> Bool{
        if(isMedia(message)){
            guard let content = message.content as? OsnMediaMessageContext else {return false}
            return !content.decKey.isEmpty && content.localPath.isEmpty
        }
        return false
    }
    func isMedia(_ message:DMCCMessage)->Bool{
        return message.msgType == OsnMessageContentType.image
        || message.msgType == OsnMessageContentType.video
        || message.msgType == OsnMessageContentType.voice
        || message.msgType == OsnMessageContentType.sticker
        || message.msgType == OsnMessageContentType.file
    }
    func updateMessageStatus(_ hash:String, _ status:OsnMessageStatus) -> Bool {
        return dbMessage.updateMessageInfo(hash, dbMessage._status <- status.rawValue)
    }
    
    func searchMessage(_ osnId:String,_ text:String) -> [DMCCMessage] {
        return dbMessage.searchMessage(osnId, text, 0, OsnUtils.getTimeStamp())
    }
    func searchUser(_ text:String)->[DMCCUser]{
        return dbUser.searchUser(text)
    }
    func searchGroup(_ text:String)->[DMCCGroup]{
        return dbGroup.searchGroup(text)
    }
}

